package org.hk.proxy.client.handlers;

import cn.hutool.core.util.ByteUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import org.hk.proxy.client.ClientChannelManagement;
import org.hk.proxy.client.listener.ChannelStatusListener;
import org.hk.proxy.client.listener.ProxyChannelBorrowListener;
import org.hk.proxy.common.constant.CommonKey;
import org.hk.proxy.common.constant.ResultCode;
import org.hk.proxy.protocol.ProxyMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class ClientChannelHandler  extends SimpleChannelInboundHandler<ProxyMessage> {
    /**
     * 连接lan端的bootstrap
     */
    private Bootstrap realServerBootstrap;
    private Bootstrap proxyBootStrap;
    private ChannelStatusListener channelStatusListener;
    private static Logger logger = LoggerFactory.getLogger(ClientChannelHandler.class);
    public ClientChannelHandler(Bootstrap realServerBootstrap,Bootstrap proxyBootStrap, ChannelStatusListener channelStatusListener) {
        this.realServerBootstrap=realServerBootstrap;
        this.proxyBootStrap=proxyBootStrap;
        this.channelStatusListener=channelStatusListener;
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage msg) throws Exception {
        switch (msg.getType()) {
            case ProxyMessage.C_TYPE_AUTH:
                handlerAuthMessage(ctx,msg);
                break;
            case ProxyMessage.TYPE_CONNECT : case ProxyMessage.PROXY_SOCKET5_CONNECT:
                handlerConnectMsg(ctx,msg);
                break;
            case ProxyMessage.TYPE_DISCONNECT:
                handlerDisconnect(ctx);
                break;
            case ProxyMessage.P_TYPE_TRANSFER:
                handlerTransfer(ctx,msg);
                break;
            default:
                break;
        }
    }



    private void handlerDisconnect(ChannelHandlerContext ctx){
        Channel realServerChannel = ctx.channel().attr(CommonKey.REAL_PROXY).getAndSet(null);
        if (realServerChannel != null) {
            logger.info("客户端handler接收到连接失败的消息:{}",ctx.channel());
            ClientChannelManagement.returnProxyChannel(ctx.channel());
            realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }
    private void handlerTransfer(ChannelHandlerContext ctx, ProxyMessage proxyMessage){
        Channel realServerChannel = ctx.channel().attr(CommonKey.REAL_PROXY).get();
        if (realServerChannel != null) {
            ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
            buf.writeBytes(proxyMessage.getData());
            realServerChannel.writeAndFlush(buf);
        }
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        Channel realServerChannel = ctx.channel().attr(CommonKey.REAL_PROXY).get();
        if(realServerChannel!=null){
            realServerChannel.config().setOption(ChannelOption.AUTO_READ,ctx.channel().isWritable());
        }
        super.channelWritabilityChanged(ctx);
    }

    private void handlerConnectMsg(ChannelHandlerContext ctx, ProxyMessage proxyMessage){
        String[] lanInfos = proxyMessage.getUri().split(":");
        if(lanInfos.length!=2){
            logger.error("ip error:{}",proxyMessage.getUri());
        }
        Channel cmdProxy = ctx.channel();
        this.realServerBootstrap.connect(lanInfos[0],Integer.valueOf(lanInfos[1])).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                ProxyMessage flushMsg = new ProxyMessage();
                flushMsg.setUri(proxyMessage.getUri());
                flushMsg.setSerialNumber(proxyMessage.getSerialNumber());
                if(future.isSuccess()){
                    final Channel realServerChannel = future.channel();
                    realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
                    if(proxyMessage.getType()==ProxyMessage.TYPE_CONNECT){
                        ClientChannelManagement.borrowChannel(proxyBootStrap, new ProxyChannelBorrowListener() {
                            @Override
                            public void success(Channel newProxyChannel) {
                                realServerChannel.attr(CommonKey.REAL_PROXY).set(newProxyChannel);
                                realServerChannel.attr(CommonKey.SID).set(flushMsg.getSerialNumber());
                                newProxyChannel.attr(CommonKey.REAL_PROXY).set(realServerChannel);
                                flushMsg.setType(ProxyMessage.TYPE_CONNECT);
                                newProxyChannel.writeAndFlush(flushMsg);
                                realServerChannel.config().setOption(ChannelOption.AUTO_READ,true);
                            }
                            @Override
                            public void error(Throwable cause) {
                                flushMsg.setType(ProxyMessage.TYPE_DISCONNECT);
                                cmdProxy.writeAndFlush(flushMsg);
                                logger.error("borrowListener error:",cause);
                            }
                        });
                    }else if(proxyMessage.getType()==ProxyMessage.PROXY_SOCKET5_CONNECT){
                        realServerChannel.attr(CommonKey.REAL_PROXY).set(ClientChannelManagement.cmdChannel);
                        realServerChannel.attr(CommonKey.SID).set(flushMsg.getSerialNumber());
                        ClientChannelManagement.cmdChannel.attr(CommonKey.REAL_PROXY).set(realServerChannel);
                        flushMsg.setType(ProxyMessage.PROXY_SOCKET5_CONNECT);
                        ClientChannelManagement.cmdChannel.writeAndFlush(flushMsg);
                        realServerChannel.config().setOption(ChannelOption.AUTO_READ,true);
                    }
                }else {
                    logger.error("连接失败:{},{}",lanInfos[0],lanInfos[1]);
                    proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
                    ctx.channel().writeAndFlush(proxyMessage);
                }
            }
        });
    }

    private void  handlerAuthMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage){
        if(ByteUtil.bytesToInt(proxyMessage.getData())== ResultCode.SUCCESS.getCode()){
            logger.info("连接成功:{}",ctx.channel().remoteAddress());
        }else {
            logger.info("连接失败!");
            ctx.channel().close();
        }
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("client lost connect");
        Channel proxyChannel = ctx.channel();
        Channel realChannel = proxyChannel.attr(CommonKey.REAL_PROXY).getAndSet(null);
        if(realChannel!=null&&realChannel.isActive()){
            realChannel.close();
        }
        ClientChannelManagement.proxyChannels.remove(proxyChannel);
        if(ctx.channel()==ClientChannelManagement.cmdChannel){
            this.channelStatusListener.channelInactive(ctx);
        }else {
            super.channelInactive(ctx);
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("error:{}",cause);
        if(cause instanceof IOException){
            channelInactive(ctx);
            return;
        }else {
            super.exceptionCaught(ctx,cause);
        }
    }
}
